import os
import re
import sys
import argparse
import array
from functools import reduce
from operator import xor

##############################################################################
# 
# QX C2000 DSP flash image generation tool
# 
# 1. Image layout for single bootloader chip 
#    (QXS320F280049, QXS320F28377L, QXS320F28335, QX320F2800137)
#    +----------------------------------------------+  offset = 0x0000
#    | 8KB bootloader                               |
#    +----------------------------------------------+  offset = 0x2000
#    | 4B CORE0 TEXT size                           |
#    +----------------------------------------------+
#    | 4B CORE0 DATA size                           |
#    +----------------------------------------------+
#    | 4B CORE1 TEXT size                           |
#    +----------------------------------------------+
#    | 4B CORE1 DATA size                           |
#    +----------------------------------------------+
#    | 1008B padding bits                           |
#    +----------------------------------------------+  offset = 0x2400
#    | Core0 TEXT                                   |
#    +----------------------------------------------+
#    | Core0 DATA                                   |
#    +----------------------------------------------+
#    | Core1 TEXT                                   |
#    +----------------------------------------------+
#    | Core1 DATA                                   |
#    +----------------------------------------------+
#
# 2. Image layout for dual bootloader chip with CLA (QX320F28P659)
#    +----------------------------------------------+  offset = 0x0000
#    | 8KB CORE0 bootloader                         |
#    +----------------------------------------------+  offset = 0x2000
#    | 8KB CORE1 bootloader                         |
#    +----------------------------------------------+  offset = 0x4000
#    | 4B CORE0 TEXT size                           |
#    +----------------------------------------------+
#    | 4B CORE0 DATA size                           |
#    +----------------------------------------------+
#    | 4B CORE1 TEXT size                           |
#    +----------------------------------------------+
#    | 4B CORE1 DATA size                           |
#    +----------------------------------------------+
#    | 4B CORE0 CLA TEXT size                       |
#    +----------------------------------------------+
#    | 4B CORE0 CLA DATA size                       |
#    +----------------------------------------------+
#    | 4B CORE1 CLA TEXT size                       |
#    +----------------------------------------------+
#    | 4B CORE1 CLA DATA size                       |
#    +----------------------------------------------+
#    | 992B padding bits                            |
#    +----------------------------------------------+  offset = 0x4400
#    | Core0 TEXT                                   |
#    +----------------------------------------------+
#    | Core0 DATA                                   |
#    +----------------------------------------------+
#    | Core1 TEXT                                   |
#    +----------------------------------------------+
#    | Core1 DATA                                   |
#    +----------------------------------------------+
#    | Core0 CLA TEXT                               |
#    +----------------------------------------------+
#    | Core0 CLA DATA                               |
#    +----------------------------------------------+
#    | Core1 CLA TEXT                               |
#    +----------------------------------------------+
#    | Core1 CLA DATA                               |
#    +----------------------------------------------+
#
# 3. Image layout for dual bootloader chip with DCSM and far SRAM (QX320F280049v2)
#    +----------------------------------------------+  offset = 0x0000
#    | 8KB CORE0 bootloader                         |
#    +----------------------------------------------+  offset = 0x2000
#    | 8KB CORE1 bootloader                         |
#    +----------------------------------------------+  offset = 0x4000
#    | 4KB for DCSM                                 |
#    +----------------------------------------------+  offset = 0x5000
#    | 4B CORE0 TEXT size                           |
#    +----------------------------------------------+
#    | 4B CORE0 DATA size                           |
#    +----------------------------------------------+
#    | 4B CORE1 TEXT size                           |
#    +----------------------------------------------+
#    | 4B CORE1 DATA size                           |
#    +----------------------------------------------+
#    | 16B Reserved (hole for CLA info)             |
#    +----------------------------------------------+
#    | 4B CORE0 FAR TEXT size                       |
#    +----------------------------------------------+
#    | 4B 0x00000000                                |
#    +----------------------------------------------+
#    | 4B CORE1 FAR TEXT size                       |
#    +----------------------------------------------+
#    | 4B 0x00000000                                |
#    +----------------------------------------------+
#    | 4B CORE0 XINTF/EMIF TEXT size                |
#    +----------------------------------------------+
#    | 4B 0x00000000                                |
#    +----------------------------------------------+
#    | 4B CORE1 XINTF/EMIF TEXT size                |
#    +----------------------------------------------+
#    | 4B 0x00000000                                |
#    +----------------------------------------------+
#    | 960B padding bits                            |
#    +----------------------------------------------+  offset = 0x5400
#    | Core0 TEXT                                   |
#    +----------------------------------------------+
#    | Core0 DATA                                   |
#    +----------------------------------------------+
#    | Core1 TEXT                                   |
#    +----------------------------------------------+
#    | Core1 DATA                                   |
#    +----------------------------------------------+
#
##############################################################################

# Flash attributes
FLASH_START_ADDRESS = 0x30000000

FLASH_PAD_WORD = 'DEADBEEF'
FLASH_PAD_LINE = FLASH_PAD_WORD + FLASH_PAD_WORD

FLASH_PAD_WORD_DCSM = 'FFFFFFFF'
FLASH_PAD_LINE_DCSM = FLASH_PAD_WORD_DCSM + FLASH_PAD_WORD_DCSM

# text section size is aligned to 256 B, according to bootloader
TEXT_LINE_ALIGNMENT = 32

FLASH_IMAGE_DAT_FILENAME = 'flash_image.dat'
FLASH_IMAGE_FILENAME     = 'flash_image.hex'
FLASH_IMAGE_BIN_FILENAME = 'flash_image.bin'

ECC_DELIMITER_STR = 'ffffffff'

class GenFlashImage:
    def __init__(self, arge, argm, argd, argcla, argdcsm):
        self.enableEcc     = arge
        self.makefileBuild = argm
        self.dualBoot      = argd
        self.hasCla        = argcla
        self.hasDcsm       = argdcsm

        # bootram has 8192 Bytes and 1024 lines in .dat file
        # one line of .dat file contains 8 Bytes.
        self.N_LINES_BOOT_RAM = 1024

        # pad 512 lines of dummy data (4096 Bytes) if has DCSM
        self.N_LINES_PAD_DCSM = 0
        if self.hasDcsm:
            self.N_LINES_PAD_DCSM = 512
        self.N_LINES_PAD_INFO = 128

        self.nBytesBootloaderText = 8 * self.N_LINES_BOOT_RAM
        self.nBytesCoreText = [0, 0]
        self.nBytesCoreData = [0, 0]
        self.nBytesClaText  = [0, 0]
        self.nBytesClaData  = [0, 0]
        self.nBytesCoreTextFar   = [0, 0]
        self.nBytesCoreDataFar   = [0, 0]
        self.nBytesCoreTextXintf = [0, 0]
        self.nBytesCoreDataXintf = [0, 0]

        self.allInstEccVal = 0

        # instruction ECC value has 7 bits of data
        self.N_BITS_ECC = 32

        # constants used for calculating instruction ECC, converted form RTL code ecc_insn.v
        # constant self.instEccConsts[i] for output ECC bit[i]
        self.instEccConsts = [
            0xb4e5b025f7011641b4e5b025,
            0xdd2ed06e19033ac3dd2ed06e,
            0x0eb810f9c50763c70eb810f9,
            0x1d7021f38a0ec78e1d7021f3,
            0x8e05f3c2e31c995d8e05f3c2,
            0xa8ee57a0313824fba8ee57a0,
            0x51dcaf40627049f651dcaf40,
            0x175ceea533e185ad175ceea5,
            0x9a5c6d6f90c21d1b9a5c6d6f,
            0x34b8dadf21843a3634b8dadf,
            0xdd94059bb409622ddd94059b,
            0x0fcdbb129f13d21b0fcdbb12,
            0xab7ec600c926b277ab7ec600,
            0x56fd8c01924d64ee56fd8c01,
            0xadfb1803249ac9dcadfb1803,
            0x5bf63006493593b85bf63006,
            0x0309d029656a31310309d029,
            0x0613a052cad462620613a052,
            0x0c2740a595a8c4c40c2740a5,
            0x184e814b2b518988184e814b,
            0x309d029656a31310309d0296,
            0x613a052cad462620613a052c,
            0x7691ba7cad8d5a017691ba7c,
            0x59c6c4dcac1ba24359c6c4dc,
            0xb38d89b958374486b38d89b9,
            0x671b1372b06e890c671b1372,
            0x7ad396c097dc04597ad396c0,
            0xf5a72d812fb808b2f5a72d81,
            0xeb4e5b025f701164eb4e5b02,
            0xd69cb604bee022c8d69cb604,
            0xad396c097dc04590ad396c09,
            0x5a72d812fb808b205a72d812
        ]

        self.instEccConstBitLength = 96

        # from RTL source code of file 'ecc_crc32.v'
        self.crcIn = 0xffffffff

        self.eccLocation = 0

        assert(len(self.instEccConsts) == self.N_BITS_ECC);

        # ram image files from which to generate flash image
        self.ramImageDict = {}
        if not self.dualBoot:
            self.ramImageDict = {
                'bootloader_iram': ''
            }
        else:
            self.ramImageDict = {
                'core0_bootloader_iram': '',
                'core1_bootloader_iram': ''
            }
        self.ramImageDict.update({
                'core0_iram': '',
                'core0_dram': '',
                'core1_iram': '',
                'core1_dram': '',
                'cla0_iram' : '',
                'cla0_dram' : '',
                'cla1_iram' : '',
                'cla1_dram' : '',
                'core0_iramfar'   : '',
                'core0_iramxintf' : '',
                'core1_iramfar'   : '',
                'core1_iramxintf' : ''
            })

        # 1024 means 8KB
        # one line contains 16 characters for 8-byte hex number
        #     h0h1h2h3h4h5h6h7
        self.N_LINE_NO_CORE0_SECTION_INFO = 1024
        if self.dualBoot:
            # another 8KB for 2nd bootloader
            self.N_LINE_NO_CORE0_SECTION_INFO += 1024
        if self.hasDcsm:
            # DCSM takes 4KB
            self.N_LINE_NO_CORE0_SECTION_INFO += 512
        # one info has .text and .data size, each takes 4B and requires 8B (one line) in total
        self.N_LINE_NO_CORE1_SECTION_INFO = self.N_LINE_NO_CORE0_SECTION_INFO + 1
        self.N_LINE_NO_CLA0_SECTION_INFO  = self.N_LINE_NO_CORE0_SECTION_INFO + 2
        self.N_LINE_NO_CLA1_SECTION_INFO  = self.N_LINE_NO_CORE0_SECTION_INFO + 3

        self.N_LINE_NO_CORE0_FAR_SECTION_INFO   = self.N_LINE_NO_CORE0_SECTION_INFO + 4
        self.N_LINE_NO_CORE1_FAR_SECTION_INFO   = self.N_LINE_NO_CORE0_SECTION_INFO + 5
        self.N_LINE_NO_CORE0_XINTF_SECTION_INFO = self.N_LINE_NO_CORE0_SECTION_INFO + 6
        self.N_LINE_NO_CORE1_XINTF_SECTION_INFO = self.N_LINE_NO_CORE0_SECTION_INFO + 7

        if not self.dualBoot and self.hasCla:
            print("\nSingle bootloader, CLA image is not supported now.\n")
            sys.exit(-1)

    def calcEccForBit(self, nbit, i64data):
        n96bitdata = (i64data << 32) | self.crcIn
        xorbits = []
        for i in range(0, self.instEccConstBitLength):
            if (self.instEccConsts[nbit] & (0x1 << i)) != 0:
                xorbits.append((n96bitdata >> i) & 0x1)
        return reduce(xor, xorbits)

    def calcEccForWord(self, i32bitInst):
        lWordEccVal = 0
        i64data = (i32bitInst << 32) | self.allInstEccVal
        for i in range(0, self.N_BITS_ECC):
            lWordEccVal |= self.calcEccForBit(i, i64data) << i
        # assign back to global inst ecc value for the subsequent round of calculation
        self.allInstEccVal = lWordEccVal
        #print(f"i32bitInst = 0x{i32bitInst:0>8x}, ECC = 0x{self.allInstEccVal:0>8x}")

    def calcEccForDatFile(self, datFileLines):
        for dfline in datFileLines:
            lo32bitsVal = int(dfline[8:16], 16)
            hi32bitsVal = int(dfline[0: 8], 16)
            self.calcEccForWord(lo32bitsVal)
            self.calcEccForWord(hi32bitsVal)

    def getSectionSizeInfo(self, x1, x2):
        x = hex(x1)[2:]
        x3 = x.zfill(8)
        x = hex(x2)[2:]
        x4 = x.zfill(8)
        y = x3 + x4 + '\n'
        return y

    def addSection(self, srcList, dstList):
        nBytes = 0
        for line in srcList:
            nBytes = nBytes + 8
            dstList.append(line)
        return nBytes

    def addBootloader(self, srcList, dstList):
        lineNum = 0
        for line in srcList:
            lineNum = lineNum + 1
            dstList.append(line)

        # bootloader code size must be in 8KB range
        # there are 8 bytes per line, so we / 8
        assert(lineNum <= self.N_LINES_BOOT_RAM)

        padLinesNum = self.N_LINES_BOOT_RAM - lineNum
        for i in range(0, padLinesNum):
            dstList.append(FLASH_PAD_LINE + '\n')
            srcList.append(FLASH_PAD_LINE + '\n')

    def addInfoPad(self,  dstList):
        for i in range(0, self.N_LINES_PAD_DCSM):
            dstList.append(FLASH_PAD_LINE_DCSM + '\n')
        for i in range(0, self.N_LINES_PAD_INFO):
            dstList.append(FLASH_PAD_LINE + '\n')

    def addSectionSizeInfo(self, dstList, info1, info2, pos):
        coreInfo = self.getSectionSizeInfo(info1, info2)
        dstList[pos] = coreInfo

    def addCode(self, dataList, txtList, dstList, coreno, pos, isAppendEcc):
        self.nBytesCoreText[coreno] = self.addSection(txtList, dstList)
        if self.enableEcc and isAppendEcc:
            eccList = [hex(self.allInstEccVal)[2:].zfill(8) + ECC_DELIMITER_STR + '\n']
            self.nBytesCoreText[coreno] += self.addSection(eccList, dstList)
        if self.enableEcc:
            assert((self.nBytesCoreText[coreno] % (TEXT_LINE_ALIGNMENT*8)) == 0)
        if len(txtList) != 0:
            infoStr = '  core' + str(coreno) + f' text section: {self.nBytesCoreText[coreno]:>6d} Bytes (0x{self.nBytesCoreText[coreno]:0>6x})'
            print(infoStr)

        self.nBytesCoreData[coreno] = self.addSection(dataList, dstList)
        if len(txtList) != 0:
            infoStr = '  core' + str(coreno) + f' data section: {self.nBytesCoreData[coreno]:>6d} Bytes (0x{self.nBytesCoreData[coreno]:0>6x})'
            print(infoStr)

        self.addSectionSizeInfo(dstList, self.nBytesCoreData[coreno], self.nBytesCoreText[coreno], pos)

    def addClaCode(self, dataList, txtList, dstList, coreno, pos, isAppendEcc):
        self.nBytesClaText[coreno] = self.addSection(txtList, dstList)
        if self.enableEcc and isAppendEcc:
            eccList = [hex(self.allInstEccVal)[2:].zfill(8) + ECC_DELIMITER_STR + '\n']
            self.nBytesClaText[coreno] += self.addSection(eccList, dstList)
        if self.enableEcc:
            assert((self.nBytesClaText[coreno] % (TEXT_LINE_ALIGNMENT*8)) == 0)
        if len(txtList) != 0:
            infoStr = '   cla' + str(coreno) + f' text section: {self.nBytesClaText[coreno]:>6d} Bytes (0x{self.nBytesClaText[coreno]:0>6x})'
            print(infoStr)

        self.nBytesClaData[coreno] = self.addSection(dataList, dstList)
        if len(txtList) != 0:
            infoStr = '   cla' + str(coreno) + f' data section: {self.nBytesClaData[coreno]:>6d} Bytes (0x{self.nBytesClaData[coreno]:0>6x})'
            print(infoStr)

        self.addSectionSizeInfo(dstList, self.nBytesClaData[coreno], self.nBytesClaText[coreno], pos)
    
    def addCodeFar(self, dataList, txtList, dstList, coreno, pos, isAppendEcc):
        self.nBytesCoreTextFar[coreno] = self.addSection(txtList, dstList)
        if self.enableEcc and isAppendEcc:
            eccList = [hex(self.allInstEccVal)[2:].zfill(8) + ECC_DELIMITER_STR + '\n']
            self.nBytesCoreTextFar[coreno] += self.addSection(eccList, dstList)
        if self.enableEcc:
            assert((self.nBytesCoreTextFar[coreno] % (TEXT_LINE_ALIGNMENT*8)) == 0)
        infoStr = '  core' + str(coreno) + f' textfar section: {self.nBytesCoreTextFar[coreno]:>6d} Bytes (0x{self.nBytesCoreTextFar[coreno]:0>6x})'
        print(infoStr)

        self.addSectionSizeInfo(dstList, self.nBytesCoreDataFar[coreno], self.nBytesCoreTextFar[coreno], pos)
    
    def addCodeXintf(self, dataList, txtList, dstList, coreno, pos, isAppendEcc):
        self.nBytesCoreTextXintf[coreno] = self.addSection(txtList, dstList)
        if self.enableEcc and isAppendEcc:
            eccList = [hex(self.allInstEccVal)[2:].zfill(8) + ECC_DELIMITER_STR + '\n']
            self.nBytesCoreTextXintf[coreno] += self.addSection(eccList, dstList)
        if self.enableEcc:
            assert((self.nBytesCoreTextXintf[coreno] % (TEXT_LINE_ALIGNMENT*8)) == 0)
        infoStr = '  core' + str(coreno) + f' textxintf section: {self.nBytesCoreTextXintf[coreno]:>6d} Bytes (0x{self.nBytesCoreTextXintf[coreno]:0>6x})'
        print(infoStr)

        self.addSectionSizeInfo(dstList, self.nBytesCoreDataXintf[coreno], self.nBytesCoreTextXintf[coreno], pos)

    def alignImageLines(self, textlines, isAppendEcc):
        lenMod = len(textlines) % TEXT_LINE_ALIGNMENT
        if (lenMod == 0) and (not isAppendEcc):
            return

        lNPadLines = TEXT_LINE_ALIGNMENT - lenMod;
        if isAppendEcc:
            # pad one less line for ecc str
            lNPadLines -= 1
        for i in range(0, lNPadLines):
            textlines.append(FLASH_PAD_LINE + '\n')

    def genRamImageDict(self, coreDatBuildDir):
        ramImageBase = [
            'iram_image.core0.dat', 'dram_image.core0.dat',
            'iram_image.core1.dat', 'dram_image.core1.dat' ]
        if not self.dualBoot:
            ramImageBase = ['iram_image.bootloader.dat'] + ramImageBase
        else:
            ramImageBase = ['core0.iram_image.bootloader.dat', 'core1.iram_image.bootloader.dat'] + ramImageBase
        if self.hasCla:
            ramImageBase = ramImageBase + ['iram_image.cla0.dat', 'dram_image.cla0.dat', 'iram_image.cla1.dat', 'dram_image.cla1.dat']
        # always search iramfar and iramxintf (EMIF) .dat files
        ramImageBase = ramImageBase + ['iramfar_image.core0.dat', 'iramxintf_image.core0.dat', 'iramfar_image.core1.dat', 'iramxintf_image.core1.dat']
        ramImages = [os.path.join(root, ff) for root, dirs, files in os.walk('.') for ff in files if os.path.splitext(ff)[1] == '.dat' and ff in ramImageBase]

        for imagefile in ramImages:
            splitpath = os.path.normpath(imagefile).split(os.sep)
            splitfile = re.split(r'[_|.]', splitpath[-1])

            bootloaderLocation = 2
            if self.dualBoot:
                bootloaderLocation = 3

            key = splitfile[2] + '_' + splitfile[0]
            if self.dualBoot and splitfile[bootloaderLocation] == 'bootloader':
                key = splitfile[0] + '_' + splitfile[3] + '_' + splitfile[1]
            #print(key, imagefile)
            self.ramImageDict[key] = imagefile

    def loadDatFile(self, datFileName):
        datFileLines = []
        if len(datFileName) != 0 and os.path.exists(datFileName):
            with open(datFileName) as infile:
                datFileLines = infile.readlines()
        return datFileLines

    def genDatFile(self):
        coreDatBuildDir = 'Build'
        if not self.makefileBuild:
            coreDatBuildDir = 'Release'
        self.genRamImageDict(coreDatBuildDir)
        #for k, v in self.ramImageDict.items():
        #    print(k,v)

        if (not self.dualBoot) and len(self.ramImageDict['bootloader_iram']) == 0:
            print('Warning: Build bootloader first to generate flash image.')
            sys.exit(-1)

        if len(self.ramImageDict['core0_iram']) == 0 or len(self.ramImageDict['core0_dram']) == 0:
            print('Warning: Build core0 first to generate flash image.')
            sys.exit(-1)

        if not self.dualBoot:
            core0BootLines = self.loadDatFile(self.ramImageDict['bootloader_iram'])
        else:
            core0BootLines = self.loadDatFile(self.ramImageDict['core0_bootloader_iram'])
            core1BootLines = self.loadDatFile(self.ramImageDict['core1_bootloader_iram'])

        core0DataFileLines = self.loadDatFile(self.ramImageDict['core0_dram'])
        core0TextFileLines = self.loadDatFile(self.ramImageDict['core0_iram'])
        core1DataFileLines = self.loadDatFile(self.ramImageDict['core1_dram'])
        core1TextFileLines = self.loadDatFile(self.ramImageDict['core1_iram'])

        cla0TextFileLines = []
        cla1TextFileLines = []
        if self.dualBoot and self.hasCla:
            cla0DataFileLines = self.loadDatFile(self.ramImageDict['cla0_dram'])
            cla0TextFileLines = self.loadDatFile(self.ramImageDict['cla0_iram'])
            cla1DataFileLines = self.loadDatFile(self.ramImageDict['cla1_dram'])
            cla1TextFileLines = self.loadDatFile(self.ramImageDict['cla1_iram'])

        core0DataFarFileLines = []
        core0TextFarFileLines = self.loadDatFile(self.ramImageDict['core0_iramfar'])
        core1DataFarFileLines = []
        core1TextFarFileLines = self.loadDatFile(self.ramImageDict['core1_iramfar'])

        core0DataXintfFileLines = []
        core0TextXintfFileLines = self.loadDatFile(self.ramImageDict['core0_iramxintf'])
        core1DataXintfFileLines = []
        core1TextXintfFileLines = self.loadDatFile(self.ramImageDict['core1_iramxintf'])

        # NOTE: the sequence here should match bootloader copy sequence, and
        #       the sequence inside should match flash to IRAM data sequence
        if self.enableEcc:
            if not self.dualBoot:
                # calculate ECC value for core0 and core1 instructions
                # ECC must be calculated with all IRAM data (including padded data) without
                # ECC_DELIMITER_STR and ECC value itself
                self.eccLocation = self.getEccLocation(len(core1TextFileLines), 0, 0)
                self.alignImageLines(core0TextFileLines, 0 == self.eccLocation)
                self.alignImageLines(core1TextFileLines, 1 == self.eccLocation)
                self.calcEccForDatFile(core0TextFileLines)
                self.calcEccForDatFile(core1TextFileLines)
            else:
                # calculate ECC value for all RAM data, without ECC_DELIMITER_STR and ECC value itself
                self.eccLocation = self.getEccLocation(len(core1TextFileLines), len(cla0TextFileLines), len(cla1TextFileLines))
                self.alignImageLines(core0DataFileLines, 0)
                self.alignImageLines(core0TextFileLines, 0 == self.eccLocation)
                self.alignImageLines(core1DataFileLines, 0)
                self.alignImageLines(core1TextFileLines, 1 == self.eccLocation)
                if self.hasCla:
                    self.alignImageLines(cla0DataFileLines , 0)
                    self.alignImageLines(cla0TextFileLines , 2 == self.eccLocation)
                    self.alignImageLines(cla1DataFileLines , 0)
                    self.alignImageLines(cla1TextFileLines , 3 == self.eccLocation)
                self.calcEccForDatFile(core0DataFileLines)
                self.calcEccForDatFile(core0TextFileLines)
                self.calcEccForDatFile(core1DataFileLines)
                self.calcEccForDatFile(core1TextFileLines)
                if self.hasCla:
                    self.calcEccForDatFile(cla0DataFileLines)
                    self.calcEccForDatFile(cla0TextFileLines)
                    self.calcEccForDatFile(cla1DataFileLines)
                    self.calcEccForDatFile(cla1TextFileLines)
        
        packageList = []
        self.addBootloader(core0BootLines, packageList)
        if self.dualBoot:
            self.addBootloader(core1BootLines, packageList)
        self.addInfoPad(packageList)

        self.addCode(core0DataFileLines, core0TextFileLines, packageList, 0, self.N_LINE_NO_CORE0_SECTION_INFO, 0 == self.eccLocation)
        self.addCode(core1DataFileLines, core1TextFileLines, packageList, 1, self.N_LINE_NO_CORE1_SECTION_INFO, 1 == self.eccLocation)
        if self.hasCla:
            self.addClaCode(cla0DataFileLines , cla0TextFileLines , packageList, 0, self.N_LINE_NO_CLA0_SECTION_INFO , 2 == self.eccLocation)
            self.addClaCode(cla1DataFileLines , cla1TextFileLines , packageList, 1, self.N_LINE_NO_CLA1_SECTION_INFO , 3 == self.eccLocation)

        if len(core0DataFarFileLines) != 0 and len(core0TextFarFileLines) != 0:
            self.addCodeFar(core0DataFarFileLines, core0TextFarFileLines, packageList, 0, self.N_LINE_NO_CORE0_FAR_SECTION_INFO, 0)
        if len(core1DataFarFileLines) != 0 and len(core1TextFarFileLines) != 0:
            self.addCodeFar(core1DataFarFileLines, core1TextFarFileLines, packageList, 1, self.N_LINE_NO_CORE1_FAR_SECTION_INFO, 0)

        if len(core0DataXintfFileLines) != 0 and len(core0TextXintfFileLines) != 0:
            self.addCodeXintf(core0DataXintfFileLines, core0TextXintfFileLines, packageList, 0, self.N_LINE_NO_CORE0_XINTF_SECTION_INFO, 0)
        if len(core1DataXintfFileLines) != 0 and len(core1TextXintfFileLines) != 0:
            self.addCodeXintf(core1DataXintfFileLines, core1TextXintfFileLines, packageList, 1, self.N_LINE_NO_CORE1_XINTF_SECTION_INFO, 0)

        if self.enableEcc:
            eccLocationName = self.getEccLocationName(self.eccLocation)
            print(f"  Put Flash ECC (value = 0x{self.allInstEccVal:>08x}) at [{eccLocationName:>s}]")

        with open(FLASH_IMAGE_DAT_FILENAME, 'w') as fdat:
            for line in packageList:
                fdat.writelines(line)

    # for dual-boot chip, currently 28P6x, calculate which section to pad ECC value and terminate flag
    # return value:
    #   0 - padd ECC and terminator at the end of core0Text
    #   1 - padd ECC and terminator at the end of core1Text
    #   2 - padd ECC and terminator at the end of cla0Text
    #   3 - padd ECC and terminator at the end of cla1Text
    #   others - impossible
    def getEccLocation(self, core1TextLen, cla0TextLen, cla1TextLen):
        retval = -1
        if sum([core1TextLen, cla0TextLen, cla1TextLen]) == 0:
            retval = 0
        elif sum([cla0TextLen, cla1TextLen]) == 0:
            retval = 1
        elif sum([cla1TextLen]) == 0:
            retval = 2
        else:
            retval = 3
        return retval

    def getEccLocationName(self, location):
        switch = {
            0 : 'the end of core0Text',
            1 : 'the end of core1Text',
            2 : 'the end of cla0Text',
            3 : 'the end of cla1Text'
        }
        return switch.get(location)

    def genHexFile(self):
        with open(FLASH_IMAGE_DAT_FILENAME, 'r') as fdat:
            fdatLines = fdat.readlines()
            with open(FLASH_IMAGE_FILENAME, 'w') as fhex:
                for line in fdatLines:
                    line = line[:-1]
                    line = line[-8:-6] + '\n' + line[-6:-4] + '\n' + line[-4:-2] + '\n' + line[-2:] +  '\n' + line[-16:-14] + '\n' + line[-14:-12] + '\n' + line[-12:-10] + '\n' + line[-10:-8] + '\n'
                    fhex.writelines(line)

    def genBinFile(self):
        data32bitarray = []
        with open(FLASH_IMAGE_DAT_FILENAME, 'r') as fdat:
            for line in fdat:
                word32bitLo = int(line[8:16],16)
                word32bitHi = int(line[0:8] ,16)
                data32bitarray.append(word32bitLo)
                data32bitarray.append(word32bitHi)
        with open(FLASH_IMAGE_BIN_FILENAME, "wb") as fbin:
            array.array('I', data32bitarray).tofile(fbin)

    def genCompactHexFile(self):
        allDatLines = []
        with open(FLASH_IMAGE_DAT_FILENAME, 'r') as fdat:
            fdatLines = fdat.readlines()
            for line in fdatLines:
                allDatLines.append(line[:-1])

        numOfLine = len(allDatLines)
        assert(numOfLine % 2 == 0)

        CURRENT_FLASH_ADDRESS  = FLASH_START_ADDRESS
        FLAG_LAST_LINE_SKIPPED = False
        with open(FLASH_IMAGE_FILENAME, 'w') as fhex:
            fhex.write(':format=compact\n')
            fhex.write('@' + hex(CURRENT_FLASH_ADDRESS) + '\n')
            for ii in range(0, numOfLine, 2):
                word0Str = allDatLines[ii][8:16]
                word1Str = allDatLines[ii][0:8]
                word2Str = allDatLines[ii+1][8:16]
                word3Str = allDatLines[ii+1][0:8]
                if (word0Str == FLASH_PAD_WORD and word1Str == FLASH_PAD_WORD and word2Str == FLASH_PAD_WORD and word3Str == FLASH_PAD_WORD) or (word0Str == FLASH_PAD_WORD_DCSM and word1Str == FLASH_PAD_WORD_DCSM and word2Str == FLASH_PAD_WORD_DCSM and word3Str == FLASH_PAD_WORD_DCSM):
                    CURRENT_FLASH_ADDRESS += 16
                    FLAG_LAST_LINE_SKIPPED = True
                else:
                    assert((word0Str != FLASH_PAD_WORD and word1Str != FLASH_PAD_WORD and word2Str != FLASH_PAD_WORD and word3Str != FLASH_PAD_WORD) or (word0Str != FLASH_PAD_WORD_DCSM and word1Str != FLASH_PAD_WORD_DCSM and word2Str != FLASH_PAD_WORD_DCSM and word3Str != FLASH_PAD_WORD_DCSM))
                    if FLAG_LAST_LINE_SKIPPED:
                        fhex.write('@' + hex(CURRENT_FLASH_ADDRESS) + '\n')
                    line2write = word0Str + ' ' + word1Str + ' ' + word2Str + ' ' + word3Str + '\n'
                    fhex.write(line2write)
                    CURRENT_FLASH_ADDRESS += 16
                    FLAG_LAST_LINE_SKIPPED = False

def generateMemoryMapping4Rtlsim(ldMemoryFile, rtlHeaderFile):
    with open(ldMemoryFile, 'r') as infile, open(rtlHeaderFile, 'w') as outfile:
        for line in infile:
            if (not 'RAM_CORE' in line) or ('_IVEC' in line):
                continue
            line = line.strip().replace(' ', '')
            splitline = re.split(r'[:|=|,]', line)
            splitline[2] = "0x" + "{:08x}".format(int(splitline[2],16) & 0xFFFFFFF8)
            rtline = '`define ' + '{0:<20}'.format(splitline[0]) + ' ' + splitline[2].replace('0x', '32\'h')
            outfile.write(rtline + '\n')

if __name__=="__main__":
    versionStr = '1.1.6.20250701.fmt'

    parser = argparse.ArgumentParser(
        prog = 'gen_flash_image',
        description = 'generate flash image for DSP application, v' + versionStr)
    parser.add_argument('-e', '--enable-ecc', action='store_true', default=False,
                        help='enable instruction code ECC')
    parser.add_argument('-m', '--makefile-build', action='store_true', default=False,
                        help='build from makefile, instead of from IDE')
    parser.add_argument('-d', '--dual-boot', action='store_true', default=False,
                        help='generate flash image for chip with two bootloader')
    parser.add_argument('--cla', action='store_true', default=False,
                        help='generate flash imahe for chip with CLA')
    parser.add_argument('--dcsm', action='store_true', default=False,
                        help='generate flash imahe for chip with DCSM')
    parser.add_argument('--memory-vh', action='store_true', default=False,
                        help='generate ldscript_Memory.vh for RTL simulator')
    parser.add_argument('--compact-hex', action='store_true', default=False,
                        help='generate more compact .hex file')
    args = parser.parse_args()

    print("\nGenerating flash image ... (v" + versionStr + ")")
    flashImageGenerator = GenFlashImage(args.enable_ecc, args.makefile_build, args.dual_boot, args.cla, args.dcsm)
    flashImageGenerator.genDatFile()
    flashImageGenerator.genHexFile()
    print("done.\n")

    if args.compact_hex:
        flashImageGenerator.genCompactHexFile()

    flashImageGenerator.genBinFile()
    
    os.remove(FLASH_IMAGE_DAT_FILENAME)

    if args.memory_vh:
        generateMemoryMapping4Rtlsim('./libs/ldscript/ldscript_Memory.ld', 'ldscript_Memory.vh')

    sys.exit(0)
